iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

嵌入式系統、硬體等常會用到位元遮罩(bitmasking),讓我們得以調整指定位元內容而不影響其他位元,這篇文章就來看要如何透過 &|^~<< 互相搭配達成。

二進制數字解讀

0b 為開頭(bbinary 代表二進制),後面接上這個數字的每個位元,最右邊的位元為第 0 個,稱為「最不重要位元(Least Significant Bit, LSB)」,依序往左為第 123、... 個位元,直到最左邊的稱為「最重要位元(Most Significant Bit, MSB)」。

例如十進位的 1 在二進位中是 0b 0000 0001,其中除第 0 個位元 LSB 為 1 外,其餘位元皆為 0

https://ithelp.ithome.com.tw/upload/images/20260811/20178513SGAimYykWR.png

位元運算符

除了在二進制中是 0b 0000 00011,我們再假設有兩個數字:

  1. a0b 1011 1000(第三位元為 1
  2. b0b 1011 0000(第三位元為 0

位元 AND &

這個 & 要分兩種情形來看,可以用來把指定位元設成 0,或者查看指定位元內容。

透過 & 把指定位元設成 0

寫法如下,留意括弧前有 ~ 符號,也就是進行 & 運算前要先反轉每個位元:

a &= ~(1 << 3);
或
b &= ~(1 << 3);

流程為:

  1. (1 << 3)1(也就是 0b 0000 0001)往左移 3 位元,變成 0b 0000 1000
  2. ~ 反轉位元,把 0b 0000 1000 變成 0b 1111 0111
  3. &=,相當於 a &= 0b 1111 0111 如下(每個位元上下比較 1&1 = 11&0 = 00&1 = 00&0 = 0):
a = 0b 1011 1000
  & 0b 1111 0111
----------------
    0b 1011 0000  // a 的新值

或者 b & 0b 1111 0111 如下:

b = 0b 1011 0000
  & 0b 1111 0111
----------------
    0b 1011 0000 // b 的新值

由上述分析,得:

  1. 第三位元為 1 時,位元變為 0
  2. 第三位元為 0 時,位元仍為 0

由此可知 & 搭配 ~ 的功能為把指定位元變成 0

透過 & 查看指定位元內容

寫法如下,留意括弧前沒有 ~ 符號、& 後面沒有 =

a & (1 << 3);
或
b & (1 << 3);

流程如下,沒有反轉位元過程:

  1. (1 << 3)1(也就是 0b 0000 0001)往左移 3 位元,變成 0b 0000 1000
  2. &,這裡沒有 = 是因為沒有要改變 a 的值,所以不須要把運算結果透過 = 賦值回去 a,相當於直接做 a & 0b 0000 1000 如下(每個位元上下比較 1&1 = 11&0 = 00&1 = 00&0 = 0):
a = 0b 1011 1000
  & 0b 0000 1000
----------------
    0b 0000 1000 // 不拿這個值去改變 a

而如果是操作 b & 0b 0000 1000 則如下,一樣沒有 = 把新產生的值賦值回 b

b = 0b 1011 0000
  & 0b 0000 1000
----------------
    0b 0000 1000 // 不拿這個值去改變 b

由上述分析,得:

  1. 第三位元為 1 時,位元依然是 1
  2. 第三位元為 0 時,位元依然是 0

由此可知 & 但不帶 ~ 時是單純查看指定位元內容,而查看時不用改變數值,因此不用 = 賦值回去原數字。

位元 OR |

用來把指定位元設成 1,例如:

a |= (1 << 3);
或
b |= (1 << 3);

流程為:

  1. (1 << 3)1(也就是 0b 0000 0001)往左移 3 位元,變成 0b 0000 1000
  2. |=,相當於 a |= 0b 1111 0111 如下(每個位元上下比較 1|1 = 11|0 = 10|1 = 10|0 = 0):
a = 0b 1011 1000
  | 0b 0000 1000
----------------
    0b 1011 1000 // a 的新值

或者 b | 0b 1111 0111 如下:

b = 0b 1011 0000
  | 0b 0000 1000
----------------
    0b 1011 1000 // b 的新值

由上述分析,得:

  1. 第三位元為 1 時,位元仍為 1
  2. 第三位元為 0 時,位元變為 1

由此可知 | 的功能是把指定位元變成 1

位元 XOR ^

用來反轉指定位元,例如:

a ^= (1 << 3);
或
b ^= (1 << 3);

流程為:

  1. (1 << 3)1(也就是 0b 0000 0001)往左移 3 位元,變成 0b 0000 1000
  2. ^=,相當於 a ^= 0b 0000 1000 如下(每個位元上下比較 1^1 = 01^0 = 10^1 = 10^0 = 0):
a = 0b 1011 1000
  ^ 0b 0000 1000
----------------
    0b 1011 0000 // a 的新值

或者 b ^ 0b 0000 1000 如下:

b = 0b 1011 0000
  ^ 0b 0000 1000
----------------
    0b 1011 1000 // b 的新值

由上述分析,得:

  1. 第三位元為 1 時,位元變為 0
  2. 第三位元為 0 時,位元變為 1

由此可知 | 的功能是把指定位元反轉。

小結

  1. x &= ~(1 << n):把 x 的第 n 個位元強制改成 0
  2. x & (1 << n):查看 x 的第 n 個位元內容。
  3. x |= (1 << n):把 x 的第 n 個位元強制改成 1
  4. x ^= (1 << n):把 x 的第 n 個位元反轉。

可以用巨集表示如下:

#define CLEAR_BIT(p,n) (p &= ~(1 << n))
#define CHECK_BIT(p,n) (p & (1 << n)
#define SET_BIT(p,n) (p |= (1 << n))
#define FLIP_BIT(p,n) (p ^= (1 << n))

或者先把左移寫到巨集、再用函數操作位元遮罩如下:

#define BIT3 (0x1 << 3)  // 先在巨集定義左移三位元
                         // 表示要對後續變數 a 的第三個位元進行操作

static int a;

// 再於下列函數對變數 a 做位元遮罩
void clear_bit3(void) {
   a &= ~BIT3;
}

void check_bit3(void) {
   a & BIT3;
}

void set_bit3(void) {
  a |= BIT3;
}

void flip_bit3(void) {
  a ^= BIT3;
}

參考資料

  1. 韌體工程師的0x10個問題
  2. A ‘C’ Test: The 0x10 Best Questions for Would-be Embedded Programmers
  3. Bitmasking In C

上一篇
Day 10 - 指標與整數間的型別轉換
下一篇
Day 12 - 用位元運算做整數加法
系列文
韌體工程師的不只 0x10 個問題19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言